Most commercial and enterprise application software uses external libraries to perform different computer-related functionality, and the usage of external, shared libraries is a widely accepted paradigm for most programming languages and many computer operating systems. Shared libraries typically represent implementations of standard functions that are used by multiple applications and implemented in a specific programming language (for example, in C, C+, or Java). Shared libraries usually represent a collection on functions around a certain topic and therefore may provide, for example, a graphics library (e.g., with graphics rendering functionality), help and logging functions, database routines, network-based communication, interfaces with internal or external computer-based resources, etc.
External, shared libraries are referred to by a variety of names. For instance, Windows-based operating systems use the term “DLL” (Dynamic Load Library), Unix-based operating systems oftentimes refer to “Shared Libraries” or “Shared Objects”, etc. Some computer systems have embedded external shared library technology via the use of, for example, the Executable and Linkable Format (ELF) on Unix-based systems, which enables (among other techniques) the notion of a shared library concept.
Several common programming techniques are implemented to allow functions defined in external libraries to be used in these and other contexts. These techniques include, among other things, static binding, dynamic linking, and deferred loading approaches.
In general, applications using external libraries expect dependencies to be resolved during linking time, and the language statements are to be translated (oftentimes individually) into sequences of instructions that a machine can understand. These instructions can be packaged in or accessed by an executable binary. This process can be time consuming, however, as it may be necessary to package entire libraries at a time, repackage an entire library when only one function or other symbol therein changes, etc.
Furthermore, during execution of a binary, and depending on the implementation approach selected, references to external libraries in the code can be resolved by the processor, which may trigger loading of the entire library into the process space to execute the function calls. In at least some of such cases, the referenced libraries during execution may exist in the process space, irrespective of whether the application is invoking or referring to anything in those libraries, thereby potentially requiring a significant amount of overhead (e.g., memory space). Oftentimes, no decision will be made as to when or how to unload libraries that are unused and/or not referenced by the application binary, thereby requiring a large and potentially increasing amount of overhead.
These and/or other problems may exist with the various programming paradigms used in connection with external shared libraries. Using a static binding approach, for example, external function definitions typically are identified in and made available via a header file or the like, and bundled together during compilation of the main (typically calling) application. The linker used at compile time of the main application resolves the addresses or locations of the external library functions. Unfortunately, however, the static binding approach can increase the size of the application, e.g., because of the bundling together of the required external libraries.
It will be appreciated that one standard way of using shared libraries typically includes loading all dependent libraries at once into the process space of the executing (e.g., main) application, resolving all references from the main application into functions of the shared libraries (as well as the function calls from just-loaded shared libraries into yet again different shared libraries, etc.) until all open references are resolved. Unloading typically only happens at program termination. Dependencies between different shared libraries increases the difficulty that arises related to different versions of these libraries and their compatibility, and this has even been termed “DLL Hell” by some.
When dynamic linking is implemented, external function names are checked at compile time of the main application, but the shared libraries are not statically linked to and part of the final resulting application. Instead, the shared libraries are loaded at runtime, e.g., during the initialization phase of the main application in a more “on-demand” manner. Unfortunately, however, the associated code maintenance and linking management sometimes provides an opportunity for the introduction of errors of different magnitudes. In this regard, unwanted changes in the function code could cause errors, or the initialization (the linker phase at initialization time) could terminate abnormally with an error, causing the application to be unable to start again.
Deferred loading typically is seen to involve the most flexible approach for loading external shared libraries and their functions, e.g., by leveraging explicit runtime calls. In this approach, the libraries are only loaded at specific times within the running application. Any errors that occur during initialization can explicitly be handled by the application, thus making the code more robust. Unfortunately, however, these advantages come at the cost of more coding effort in the main application, as it typically is not possible to merely reference the function name within the calling program and, instead, various system function calls generally are required to load and resolve the external functions. Moreover, processing overhead can increase because at least two call switches typically will be implemented, i.e., in calling and closing each function, thereby using CPU cycles. Furthermore, the required system function calls generally are platform and/or operating system specific, thereby limiting code reuse.
Thus, it will be appreciated that one step towards a more efficient way to calling functions is the notion of an inline function to be handled by the pre-processor of the compile step. This may create (depending on the complexity of the function) at each reference of the function the complete function itself. On one hand, this can lead to much better performance at runtime. But on the other hand, it typically requires more code and increases the (object) size of the program.
Regardless of the implementation selected, an application that has a dependency to an external library may require manual processes associated with understanding the different sets of foreign library versions. This too can be time-consuming and sometimes can lead to serious application downtime, e.g., to facilitate updates for the latest external library patches. Problems thus can arise in the context of mission critical and/or other software business systems.
Similarly, regardless of the implementation selected, when a change is made to an external library, it oftentimes is necessary to recompile, restart, and/or reconfigure the program using that library. For example, applications using static bindings generally will need to be recompiled and restarted. Applications using dynamic links or implementing deferred bindings may need to be restarted and/or reprogrammed, e.g., to take advantage of changes to the library, account for deprecated functionality, etc.
Consider, for example, long-running applications (e.g., a database or other central/core application of an enterprise) that cannot easily be halted or stopped for administrative purposes in order to reload or refresh a specific shared library. Refreshing of shared libraries nonetheless is still a very common task, because shared libraries are typically developed (and/or distributed) by different departments of, or compared to, an in-house information technology (IT) department of a company, come from different vendors, etc. Fixing a bug (e.g., a computer software or other error) usually would replace the existing version of a program (e.g., a shared library) with a new, corrected version. In order to activate such a new version, any occurrence of the old version usually must be unloaded from any executing application, and the new version usually must be reloaded into the address space (e.g., the runtime execution environment) of an application. This typically requires “shutting down” or terminating the original running main application, which may not always be possible or practical.
Thus, it will be appreciated that it would be desirable to improve binding techniques associated with applications using external libraries. For instance, it will be appreciated that it would be desirable to cut down on the downtime associated with application restarts and/or library reloads needed when changes are made to an external library that it uses (e.g., when a graphics library is updated and the application using that library might need to be recompiled). It also would be desirable to provide easier (e.g., less custom) coding, allow for better code reuse, realize less overhead, and/or the like.
As will be appreciated from the detailed description below, certain example embodiments allow the main application to continue to run (e.g., execute), even as shared libraries in whole or in part are modified. In this regard, in certain example embodiments, a reloading process for the libraries can be triggered automatically or under the control of the main application (e.g., dynamic reload can be performed in an automatic fashion, but control can also be provided by the main application). The involved libraries can be marked individually for replacement (e.g., including reloading), or a whole group of libraries can be assigned for a reload event. And as will be apparent from the example implementation provided below, switching from a standard shared library implementation to the approach described herein advantageously does not require source code changes in the main application code or in the code of the shared library. Instead, a library layer and adaptions within header files may be used to enable these changes.
One aspect of certain example embodiments relates to an Inline Dispatching Function Interface (IDFI) that makes use of the inline concept to improve performance and includes a small layer positioned between the calling (e.g., main) application and the called (e.g., shared) library.
Another aspect of certain example embodiments relates to an IDFI, which enables external library functions to be included declaratively as part of an imported header file associated with a program. Certain example embodiments combine the compilation operations of static linking with deferred loading, thus potentially eliminating the need for recompilations and therefore restarts.
Another aspect of certain example embodiments relates to allowing programmers to use an interface as a transitional middle layer containing a repository of external libraries that can be used by the application that is being programmed. Function definitions contained in the external libraries (e.g., foreign function definitions) may be stored in or accessible to the interface and imported during compilation time. Advantageously, this may minimize or at least reduce the application's maintenance cost by helping to avoid the need for recompilation whenever a foreign function in an external library is modified. This also can help avoid issues with possible mistyping and errors at runtime.
Based on the description below, it will be appreciated that certain example embodiments make external library function definitions available in header files or the like. This advantageously helps to ensure that that the external library functions are available to the application program during compile time, thus reducing the overhead related to linking and loading. It also will be appreciated that certain example embodiments involve deferred loading of the library functions into the process space of the application, which advantageously provides external library functions on-demand when a function is referenced in the application program. The specification of functions and/or other symbols at compile time via entries in a header file or the like also is advantageous because it reduces the likelihood of mistyping and errors at runtime. Certain example embodiments maintain a reference to all external library functions in branch tables (each of which is a table or other structure including a symbol, address, and reference count) and unloads the function from the CPU process space after all reference handles have been released by the application. For instance, in certain example embodiments, a memory allocation helper routine enables a piece of memory to be inherited between the different versions of one shared library. The IDFI of certain example embodiments supports dynamic as well as manual reloading of shared libraries.
The pre-processing approach usable with certain example embodiments helps address issues associated with using a separate pre-processing step by giving the compiler control of pre-processing. This helps with the issue of accessing private class members in an external library. Certain example embodiments implement the inline function interfaces as a substitute to the original external library function, ensuring that the behavior of the function remains the same. The substituted inline function in certain example embodiments is internally provided using a hook to find the exact function or other symbol of the external library function that is expanded during runtime, thereby reducing the overhead related to multiple function calls.
Certain example embodiments create the exact external library function with the substituted body and with the same signature and return type, thus optimizing or at least improving the linking time efficiency because the external library function and/or other symbols are now available during compilation time. The compiler checks for the correct use of the external library function's argument list and returns an error, as appropriate. In other words, with the help of the header file definitions, the IDFI of certain example embodiments can create an exact copy of the external library function as an inline call, which can provide safe type-checking and type-casting at compilation time, and provide good performance during execution at runtime.
It will be appreciated that there are advantages of having inline definitions as compared to external function calls. For example, modern compilers oftentimes recommend the use of inline definitions in a header file. When the compiler sees such a definition, it places the function type (the signature combined with the return value) and the function body in its symbol table. Furthermore, when a function is invoked, the compiler usually checks to ensure that the call is correct and the return value is being used correctly. The compiler then substitutes the function body with the complete function call, thus eliminating or at least reducing the overhead. The inline code occupies space, but if the function is small, this can actually take less space than the code generated to perform an ordinary function call (e.g., pushing arguments on the stack and doing the CALL). An inline function in a header file has a special status, and because the header file containing the function and its definition is included in every file where the function is used, multiple definition errors will not be thrown (e.g., provided that the definition is identical in all places where the inline function is included).
In certain example embodiments, a computing system comprises processing resources including at least one processor and a memory operably coupled thereto, with the processing resources being configured to maintain a process space and execute an application in connection with the process space. At least one library is usable by, but external to, the application. A function interface is statically linked to the application and dynamically linkable to the at least one library, with the function interface being transparent to the application. The function interface is configured to at least: automatically reload the at least one library into the process space during runtime in response to a detected trigger event, without interrupting processing of the application, and while also automatically synchronizing processing threads relevant to execution of the at least one library; receive, from the application and through header file directives added to the application during compilation of the application, a call for a function of the at least one library; and responsive to reception of the call, execute the call to the function
In certain example embodiments, a computing system comprises processing resources including at least one processor and a memory operably coupled thereto, with the processing resources being configured to maintain a process space and execute an application in connection with the process space. At least one library is usable by, but external to, the application. External symbols of the at least one library are statically defined at application design time and dynamically linkable to at application runtime. An interface is interposed between the application and the at least one library. The interface is configured to at least: maintain a branch table that includes, for the at least one library, an identifier of, runtime address for, and count of calls using, each callable symbol thereof; receive a change event corresponding to a detected change to the least one library while the at least one library is loaded into the process space; and responsive to reception of the change event, (a) automatically unload from the process space the at least one library, and (b) automatically reload into the process space the at least one library, such that the at least one library is dynamically modifiable.
According to certain example embodiments, the function interface may be further configured to execute the call to the function by at least: determining whether the call is for a function already loaded into the process space; in response to a determination that the call is for a function already loaded into the process space, execute the call on the already-loaded function on behalf of the application as if the already-loaded function were statically linked to the application; and in response to a determination that the call is for a function not already loaded into the process space, dynamically load the function into the process space and then execute the call on the dynamically-loaded function on behalf of the application as if the dynamically-loaded function were statically linked to the application.
It will be appreciated that the function interface itself may be implemented as a statically linked library of the application.
Corresponding methods and non-transitory computer readable storage mediums tangibly storing instructions for performing such methods also are provided by certain example embodiments, as are corresponding computer programs.
These features, aspects, advantages, and example embodiments may be used separately and/or applied in various combinations to achieve yet further embodiments of this invention.